using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;

namespace Gko.Utils
{
	public static class CloneExtensions
	{
		public static T Clone<T>(this T instance)
		{
			if (ReferenceEquals(instance, null)) {
				throw new ArgumentNullException("instance");
			}
			var type = instance.GetType();

			if (instance is ICloneable)
				return (T)((ICloneable)instance).Clone();

			var fields = new List<MemberInfo>();
			if (type.GetCustomAttributes(typeof(SerializableAttribute), false).Length == 0) {
				var t = type;
				while (t != typeof(Object)) {
					fields.AddRange(t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly));
					t = t.BaseType;
				}
			} else
				fields.AddRange(FormatterServices.GetSerializableMembers(instance.GetType()));

			var copy = FormatterServices.GetUninitializedObject(instance.GetType());
			var values = FormatterServices.GetObjectData(instance, fields.ToArray());
			FormatterServices.PopulateObjectMembers(copy, fields.ToArray(), values);

			return (T)copy;
		}
	}
}
